package com.dzy.vtd;

import com.dzy.Dom4jUtil;
import com.ximpleware.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;

import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * Created by dzy on 2018/9/2
 */
public class VTDTest1 {

  /**
   * 内存占用：22M ，速度 0.92ms/每次
   */
  private static void testXML_Dom4jSpeed() {
    StopWatch sw = new StopWatch();
    try {
      byte[] b = IOUtils.toByteArray(System.class.getResourceAsStream("/dats/doc4speedtest.xml"));
      Document doc = Dom4jUtil.getDocFromBytes(b, "UTF-8");
      assertThat(doc.getRootElement().valueOf("@Depth"), equalTo("26"));

      String[] toFindIds = "1555,841,1050,456,1653,444,409,266,258,1626".split(",");
      sw.start();
      int loopCount = 1000;
      for (int i = 0; i < loopCount; i++) {
        for (String toFindId : toFindIds) {
          String xPath = String.format("//Node[@id=%s]", toFindId);
          Element el = (Element) doc.selectSingleNode(xPath);
          assertThat(el.valueOf("@id"), equalTo(toFindId));
        }
      }
      System.out.println("testXML_Dom4jSpeed find loop used time: " + sw.getTime());
      System.out.println("avg time to find one element : " + (sw.getTime() / (double) (toFindIds.length * loopCount)) + " ms ");
      Thread.sleep(10000);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 内存占用：9M ，速度 0.66ms/每次
   * 使用 jmc.exe 可监控内存使用情况
   */
  private static void testXML_VTDSpeed() {
    StopWatch sw = new StopWatch();
    try {
      byte[] b = IOUtils.toByteArray(System.class.getResourceAsStream("/dats/doc4speedtest.xml"));
//      byte[] b = FileUtils.readFileToByteArray(new File("v:\\doc1.xml"));
      VTDGen vg = new VTDGen();
      vg.setDoc(b);
      vg.parse(true);  // set namespace awareness to true
      VTDNav vn = vg.getNav();
      AutoPilot ap = new AutoPilot(vn);
      sw.start();
      ap.selectXPath("//Node[@id=11]");
      int saveNodeIndex = -1;
      int result = -1;
      if (ap.evalXPath() != -1) {
        saveNodeIndex = vn.getCurrentIndex();
        int idIndex = vn.getAttrVal("id");
        System.out.println("idIndex="+idIndex+"  @id="+vn.toNormalizedString(idIndex)+"  , @tblName="+
          vn.toNormalizedString2(vn.getAttrVal("tblName")));
        System.out.println("saveNodeIndex＝"+saveNodeIndex);

        idIndex = vn.getAttrVal("tblName");
        System.out.println("idIndex="+idIndex);
        System.out.println("tblName : " + vn.toNormalizedString(idIndex));
        System.out.println("idIndex="+idIndex+" , @tblName = "+vn.toNormalizedString(idIndex))  ;
      }


      System.out.println("vn.getCurrentIndex()="+vn.getCurrentIndex()+"  @tblName="+vn.toNormalizedString(vn.getCurrentIndex()));

      System.out.println(vn.toNormalizedString(11263));
      vn.recoverNode(11261);
      System.out.println(vn.toNormalizedString(vn.getAttrVal("tblName")));

      sw.stop();
      System.out.println("testXML_VTDSpeed used time: " + sw.getTime());
      if(true)return;
      String[] toFindIds = "1555,841,1050,456,1653,444,409,266,258,1626".split(",");
      sw.reset();
      sw.start();
      int loopCount = 1000;
      for (int i = 0; i < loopCount; i++) {
        for (String toFindId : toFindIds) {
          String xPath = String.format("//Node[@id=%s]", toFindId);
          ap.resetXPath();
          ap.selectXPath(xPath);
          if (ap.evalXPath() != -1) {
            int idIndex = vn.getAttrVal("id");
            assertThat("vtd find id not ok: ", vn.toNormalizedString(idIndex), equalTo(toFindId));
//            System.out.println("id: " + vn.toNormalizedString(idIndex));
          } else {
            throw new Exception("not find : " + toFindId);
          }
        }
      }
      System.out.println("testXML_VTDSpeed find loop used time: " + sw.getTime());
      System.out.println("avg time to find one element : " + (sw.getTime() / (double) (toFindIds.length * loopCount)) + " ms ");
      Thread.sleep(10000);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static VTDBag readyVTDBag(InputStream ins) {
    try {
      byte[] b = IOUtils.toByteArray(ins);
      VTDBag vtdbag = new VTDBag();
      vtdbag.vtdGen= new VTDGen();
      vtdbag.vtdGen.setDoc(b);
      vtdbag.vtdGen.parse(true);  // set namespace awareness to true
      vtdbag.vtdNav = vtdbag.vtdGen.getNav();
      vtdbag.autoPilot = new AutoPilot(vtdbag.vtdNav);
      return vtdbag;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  private static Document getDoc4Test(){
    try(InputStream in = System.class.getResourceAsStream("/dats/bioinfo.xml")){
      return Dom4jUtil.getDocFromStream(in);
    } catch (Exception e){
      e.printStackTrace();
      return null;
    }
  }

  private static void testLoopXMLElmtByVTD() throws NavException {
    // VTDNav.NS : next silbing
    // VTDNav.P : go up one level
    // VTDNav.FC :  first child
    System.out.println("beg testLoopXMLElmtByVTD ...");
    VTDBag vtdBag = readyVTDBag(System.class.getResourceAsStream("/dats/bioinfo.xml"));
    int count = 0;
    // counting child elements of parlist named "par"
    int par_count = 0;
    VTDNav vn = vtdBag.vtdNav;
    if (vn.matchElement("bix")){ // match blix
// to first child named "package"
      if (vn.toElement(VTDNav.FC,"package")){
        do {
          System.out.println("package");
          // to first child named "command"
          if (vn.toElement(VTDNav.FC,"command")){
            do {
              System.out.println("command");
              if (vn.toElement(VTDNav.FC, "parlist")){
                do {
                  System.out.println("parlist");
                  if (vn.toElement(VTDNav.FC)){
                    do {
                      count++; //increment count
                      if (vn.matchElement("par"))
                        par_count++;
                    }
                    while(vn.toElement(VTDNav.NS));
                    vn.toElement(VTDNav.P);
                  }
                }
                while (vn.toElement(VTDNav.NS,"parlist"));
                vn.toElement(VTDNav.P);
              }
            }
            // to next silbing named "command"
            while (vn.toElement(VTDNav.NS, "command"));
            vn.toElement(VTDNav.P); // go up one level
          }
          else
            System.out.println(" no child element named 'command' ");
        }
        while(vn.toElement(VTDNav.NS,"package")); // to next sibling named "package"
        vn.toElement(VTDNav.P); // go up one level
      }else
        System.out.println(" no child element named 'package' ");
    } else
      System.out.println(" Root is not 'bix' ");
// print out the results
    System.out.println(" count ====> " + count);
    System.out.println(" par_count ==> " + par_count);

    //my test...
    System.out.println("beg my test...");
    if (vn.matchElement("bix")) { // match blix
      System.out.println("found bix element");
      if (vn.toElement(VTDNav.FC, "*")){
        System.out.println("found first element");
//        System.out.println("vn.toNormalizedString(vn.getCurrentIndex())="+vn.toNormalizedString(vn.getCurrentIndex()));
        do{
          System.out.println("vn.toNormalizedString(vn.getCurrentIndex())="+vn.toNormalizedString(vn.getCurrentIndex()));
        } while (vn.toElement(VTDNav.NS));
        vn.toElement(VTDNav.P);
      }
    }

    AutoPilot ap = vtdBag.autoPilot;
    try {
      ap.selectXPath("//*");
      int i = 0;
      while ((i=ap.evalXPath())!=-1){
        System.out.println(" element name is "+vn.toString(i));
      }
    } catch (XPathParseException e) {
      e.printStackTrace();
    } catch (XPathEvalException e) {
      e.printStackTrace();
    }

    vtdBag.vtdNav.toElement(VTDNav.ROOT);
    System.out.println("beg treeWalk...");
    StringBuilder sb = new StringBuilder(vtdBag.vtdNav.toNormalizedString(vtdBag.vtdNav.getCurrentIndex())).append(",");
    treeWalk(vtdBag,sb);
    StringBuilder sb2 = treeWalkDom4j(getDoc4Test());
    assertThat(sb2.toString(),equalTo(sb.toString()));
  }

  private static StringBuilder treeWalkDom4j(Document doc){
    StringBuilder sb = new StringBuilder(doc.getRootElement().getName()).append(",");
    treeWalk(doc.getRootElement(),sb);
    return sb;
  }

  private static void treeWalk(Element element,StringBuilder sb) {
    for (int i = 0, size = element.nodeCount(); i < size; i++) {
      Node node = element.node(i);
      if (node instanceof Element) {
        sb.append(node.getName()).append(",");
        treeWalk((Element) node,sb);
      }
    }
  }

  private static void treeWalk(VTDBag vtdBag,StringBuilder sb) {
    try {
      if (vtdBag.vtdNav.toElement(VTDNav.FC, "*")){
        do{
          sb.append(vtdBag.vtdNav.toNormalizedString(vtdBag.vtdNav.getCurrentIndex())).append(",");
//          System.out.println("vn.toNormalizedString(vn.getCurrentIndex())="+vtdBag.vtdNav.toNormalizedString(vtdBag.vtdNav.getCurrentIndex()));
          treeWalk(vtdBag,sb);
        } while (vtdBag.vtdNav.toElement(VTDNav.NS));
        vtdBag.vtdNav.toElement(VTDNav.P);
      }
    } catch (NavException e) {
      e.printStackTrace();
    }

  }

  public static String getObjIdHashCode(Object o){
    return String.valueOf( System.identityHashCode(o));
  }

  private static Map<String,Map> mObjId_Map = new ConcurrentHashMap();
  private static Map applyContMap(String objId){
    if (mObjId_Map.containsKey(objId)){
      return mObjId_Map.get(objId);
    } else {
      synchronized (mObjId_Map) {
        ConcurrentHashMap newMap = new ConcurrentHashMap();
        mObjId_Map.put(objId,newMap);
        return newMap;
      }
    }
  }

  /**
   * 根据Xpath查找Node,如果缓存中有记录，则直接移动vtdNav 的指针到正确位置（相当于缓存命中）
   * 如何解决 并发访问的问题？
   * 思路1： 加锁
   * 思路2： 设置多个vtdNav
   * @param vtdBag
   * @param xPath
   * @return if found then return true ,and vtdNav move to right position
   */
  private static boolean getElAutoCache(VTDBag vtdBag, String xPath) throws XPathParseException, NavException, XPathEvalException {
    boolean bRtn = false;
    String objId = getObjIdHashCode(vtdBag.vtdGen);
    Map m = applyContMap(objId);

    String cacheKey = xPath;
    Object cacheObj = m.get(cacheKey);

    if (cacheObj!=null && cacheObj instanceof Integer){
      int rtnIdx = (Integer)cacheObj;
      vtdBag.vtdNav.recoverNode(rtnIdx);
      bRtn = true;
    } else {
      vtdBag.autoPilot.selectXPath(xPath);
      if (vtdBag.autoPilot.evalXPath() != -1) {
        int saveNodeIndex = vtdBag.vtdNav.getCurrentIndex();
        m.put(cacheKey,Integer.valueOf(saveNodeIndex));
        bRtn = true;
      }
    }
    return bRtn;
  }

  private static void testCacheVTD() {
    VTDBag vtdBag = readyVTDBag(System.class.getResourceAsStream("/dats/doc4speedtest.xml"));
    try {
      String xPath = "//Node[@id=841]";
      vtdBag.autoPilot.selectXPath(xPath);
      if (vtdBag.autoPilot.evalXPath() != -1) {
        int idIndex = vtdBag.vtdNav.getAttrVal("id");
        System.out.println("id: " + vtdBag.vtdNav.toNormalizedString(idIndex));
      }

      VTDNav nav2 = vtdBag.vtdNav.cloneNav();
      if(getElAutoCache(vtdBag,"//Node[@id=842]")){
        int idIndex=vtdBag.vtdNav.getAttrVal("id");
        System.out.println("2 id: " + vtdBag.vtdNav.toNormalizedString(idIndex));

        // 此时 nav2 保持的是上一次查找的状态，所以会得到 id = 841
        idIndex=nav2.getAttrVal("id");
        System.out.println("3 id: " + nav2.toNormalizedString(idIndex));

        // 当把 本次查找的结果赋给 nav2 后，会得到 id = 842
        nav2.recoverNode( vtdBag.vtdNav.getCurrentIndex());
        idIndex=nav2.getAttrVal("id");
        System.out.println("4 id: " + nav2.toNormalizedString(idIndex));
      }

      for (int i = 0; i < 1000; i++) {
        if(getElAutoCache(vtdBag,xPath)){
          int idIndex=vtdBag.vtdNav.getAttrVal("id");
          assertThat(vtdBag.vtdNav.toNormalizedString(idIndex),equalTo("841"));
        }
      }

      StopWatch sw = new StopWatch();
      sw.start();
      String[] toFindIds = "1555,841,1050,456,1653,444,409,266,258,1626".split(",");
      int loopCount=1000;
      for (int i = 0; i < loopCount; i++) {
        for (String toFindId : toFindIds) {
          xPath = String.format("//Node[@id=%s]", toFindId);
          if(getElAutoCache(vtdBag,xPath)){
            int idIndex=vtdBag.vtdNav.getAttrVal("id");
            assertThat(vtdBag.vtdNav.toNormalizedString(idIndex),equalTo(toFindId));
          }
        }
      }
      sw.stop();
      System.out.println("testCacheVTD find loop used time: " + sw.getTime());
      System.out.println("avg time to find one element : " + (sw.getTime() / (double) (toFindIds.length * loopCount)) + " ms ");
    } catch (Exception e) {
      e.printStackTrace();
    }

  }

  private static void testDiv() {
    int i = 6500;
    System.out.println((double) 6500 / (10 * 1000));
  }

  public static void main(String[] args) throws NavException {
    if (args.length > 0) {
      if ("dom4j".equalsIgnoreCase(args[0])) {
        testXML_Dom4jSpeed();
      } else if ("vtd".equalsIgnoreCase(args[0])) {
        testXML_VTDSpeed();
        testLoopXMLElmtByVTD();
      } else if ("cache".equalsIgnoreCase(args[0])){
        testCacheVTD();
      }
    }
//    testDiv();
  }
}

class VTDBag {
  public VTDGen vtdGen;
  public VTDNav vtdNav;
  public AutoPilot autoPilot;
}